﻿/* Copyright (C) 2012 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Xml;

namespace davserv.backends.sqlite
{
    class sqlite : Backend
    {
        SQLiteConnection conn;

        public sqlite(string db_file)
        {
            conn = new SQLiteConnection(String.Format("Data Source={0};", db_file));
            conn.Open();

            string create_table = "CREATE TABLE IF NOT EXISTS wwwdocs (uri TEXT PRIMARY KEY, parent TEXT, collection BOOL, props TEXT, ctype TEXT, encoding TEXT, data BLOB)";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = create_table;
            int resp = comm.ExecuteNonQuery();

            string create_root = "INSERT OR IGNORE INTO wwwdocs VALUES (\'/\', \'\', 1, \'\', \'\', \'\', X\'\')";
            comm = conn.CreateCommand();
            comm.CommandText = create_root;
            resp = comm.ExecuteNonQuery();
        }

        ~sqlite()
        {
            conn.Close();
        }

        public override Backend.ByteResponse Get(uri uri)
        {
            string select_string = "SELECT uri, data, ctype, encoding FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = select_string;
            SQLiteDataReader dr = comm.ExecuteReader();

            if (!dr.HasRows)
                return new ByteResponse { Status = "404" };

            dr.Read();
            return new ByteResponse { Status = "200", Value = (byte[])dr.GetValue(1), ContentType = dr.GetString(2), Encoding = dr.GetString(3) };
        }

        public override Backend.Response Put(uri uri, byte[] val, string contenttype, string encoding)
        {
            string trim = "";
            if(uri.ToString() != "/")
                trim = uri.Trim().ToString();

            PropertyStringResponse creation_date = GetProperty(uri, new PropertyName { Name = "creationdate", Namespace = "DAV:" }) as PropertyStringResponse;

            string insert_string = "INSERT OR REPLACE INTO wwwdocs VALUES (\'" + uri.ToString() + "\', \'" + trim + "\', 0, \'\', \'{1}\', \'{2}\', X\'{0}\')";
            StringBuilder sb = new StringBuilder();
            foreach(byte b in val)
                sb.Append(b.ToString("X2"));
            insert_string = String.Format(insert_string, sb.ToString(), contenttype, encoding);

            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = insert_string;
            int resp = comm.ExecuteNonQuery();

            if (resp == 1)
            {
                // Set creation and modified dates
                if ((creation_date != null) && (creation_date.PropertyValue != null) && (creation_date.PropertyValue != ""))
                    SetProperty(uri, new PropertyName { Name = "creationdate", Namespace = "DAV:" }, creation_date.PropertyValue);
                else
                    SetProperty(uri, new PropertyName { Name = "creationdate", Namespace = "DAV:" }, Utils.Rfc3339Date(DateTime.Now));

                SetProperty(uri, new PropertyName { Name = "getlastmodified", Namespace = "DAV:" }, DateTime.Now.ToString("r"));

                return new Response { Status = "200" };
            }
            else
                return new Response { Status = "500" };
        }

        public override Backend.Response MkCol(uri uri)
        {
            if (uri.ToString() == "/")
                return new Response { Status = "405" };

            if (Exists(uri).Value == true)
                return new Response { Status = "405" };

            uri parent = uri.Trim();

            if (Exists(parent).Value == false)
                return new Response { Status = "409" };

            string insert_string = "INSERT INTO wwwdocs VALUES (\'" + uri.ToString() + "\', \'" + parent.ToString() + "\', 1, \'\', \'\', \'\', X\'\')";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = insert_string;
            if (comm.ExecuteNonQuery() != 1)
                return new Response { Status = "500" };

            SetProperty(uri, new PropertyName { Name = "creationdate", Namespace = "DAV:" }, Utils.Rfc3339Date(DateTime.Now));
            SetProperty(uri, new PropertyName { Name = "getlastmodified", Namespace = "DAV:" }, DateTime.Now.ToString("r"));

            return new Response { Status = "201" };
        }

        public override Backend.Response Delete(uri uri)
        {
            if (uri.ToString() == "/")
                return new Response { Status = "403" };

            string delete_string = "DELETE FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = delete_string;
            int ret = comm.ExecuteNonQuery();

            if (ret != 1)
                return new Response { Status = "404" };

            delete_string = "DELETE FROM wwwdocs WHERE uri GLOB \'" + uri.ToString() + "/*\'";
            comm = conn.CreateCommand();
            comm.CommandText = delete_string;
            comm.ExecuteNonQuery();

            return new Response { Status = "200" };
        }

        public override Backend.StringResponse GetProperty(uri uri, Backend.PropertyName prop_name)
        {
            if (prop_name.Name == "resourcetype")
            {
                BoolResponse br = IsCollection(uri);
                if ((br.Status == "200") && br.Value)
                    return new PropertyStringResponse { Status = "200", Value = "<D:resourcetype><D:collection/></D:resourcetype>", PropertyValue = "true" };
                else
                    return new StringResponse { Status = "200", Value = "<D:resourcetype/>" };
            }
            else if (prop_name.Name == "getcontentlength")
            {
                ByteResponse br = Get(uri);
                if (br.Status == "200")
                    return new PropertyStringResponse { Status = "200", Value = "<D:getcontentlength>" + br.Value.Length.ToString() + "</D:getcontentlength>", PropertyValue = br.Value.Length.ToString() };
                else
                    return new StringResponse { Status = "404", Value = "<D:getcontentlength/>" };
            }
            else if (prop_name.Name == "getcontenttype")
            {
                string select_string = "SELECT ctype, encoding FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
                SQLiteCommand comm = conn.CreateCommand();
                comm.CommandText = select_string;
                SQLiteDataReader dr = comm.ExecuteReader();

                if (!dr.HasRows)
                    return new StringResponse { Status = "404", Value = "<D:getcontenttype/>" };

                dr.Read();
                string ctype = HttpServer.MakeContentTypeHeader(new ByteResponse { ContentType = dr.GetString(0), Encoding = dr.GetString(1) });
                return new PropertyStringResponse { Status = "200", Value = "<D:getcontenttype>" + ctype + "</D:getcontenttype>", PropertyValue = ctype };
            }
            else
            {
                string select_string = "SELECT props FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
                SQLiteCommand comm = conn.CreateCommand();
                comm.CommandText = select_string;
                SQLiteDataReader dr = comm.ExecuteReader();

                if (!dr.HasRows)
                    return new StringResponse { Status = "404", Value = "<" + prop_name.Name + " xmlns=\"" + prop_name.Namespace + "\"/>" };

                dr.Read();
                string props = dr.GetString(0);

                if (props != "")
                {
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(props);

                    XmlNode node = doc.DocumentElement;
                    foreach (XmlNode n in node.ChildNodes)
                    {
                        if ((n.Name == prop_name.Name) && (n.NamespaceURI == prop_name.Namespace))
                            return new PropertyStringResponse { Status = "200", Value = prop_name.StartString() + n.InnerText + prop_name.EndString(), PropertyValue = n.InnerText };
                    }
                }

                return new StringResponse { Status = "404", Value = "<" + prop_name.Name + " xmlns=\"" + prop_name.Namespace + "\"/>" };
            }
        }

        public override Backend.Response SetProperty(uri uri, Backend.PropertyName prop_name, string val)
        {
            if ((prop_name.Name == "getcontentlength") || (prop_name.Name == "resourcetype"))
                return new Response { Status = "403" };

            string select_string = "SELECT props FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = select_string;
            SQLiteDataReader dr = comm.ExecuteReader();

            if (!dr.HasRows)
                return new StringResponse { Status = "404", Value = "<" + prop_name.Name + " xmlns=\"" + prop_name.Namespace + "\"/>" };

            dr.Read();
            string props = dr.GetString(0);

            XmlDocument doc = new XmlDocument();
            XmlNode node = null;
            bool changes_made = false;
            if (props != "")
            {
                doc.LoadXml(props);

                node = doc.DocumentElement;
                foreach (XmlNode n in node.ChildNodes)
                {
                    if ((n.Name == prop_name.Name) && (n.NamespaceURI == prop_name.Namespace))
                    {
                        n.Value = val;
                        changes_made = true;
                        break;
                    }
                }
            }
            else
            {
                XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);
                node = doc.CreateElement("prop", "DAV:");
                doc.InsertBefore(xmlDeclaration, doc.DocumentElement);
                doc.AppendChild(node);
            }

            if(!changes_made)
            {
                XmlNode new_node = doc.CreateElement(prop_name.Name, prop_name.Namespace);
                XmlText new_text = doc.CreateTextNode(val);

                node.AppendChild(new_node);
                new_node.AppendChild(new_text);
            }

            string update_string = "UPDATE wwwdocs SET props = \'" + doc.InnerXml + "\' WHERE uri IS \'" + uri.ToString() + "\'";
            comm = conn.CreateCommand();
            comm.CommandText = update_string;
            int ret = comm.ExecuteNonQuery();

            if (ret == 1)
                return new Response { Status = "200" };
            else
                return new Response { Status = "500" };
        }

        public override Backend.Response DeleteProperty(uri uri, Backend.PropertyName prop_name)
        {
            throw new NotImplementedException();
        }

        public override Backend.BoolResponse Exists(uri uri)
        {
            string select_string = "SELECT uri FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = select_string;
            SQLiteDataReader dr = comm.ExecuteReader();

            return new BoolResponse { Status = "200", Value = dr.HasRows };
        }

        public override Backend.BoolResponse IsCollection(uri uri)
        {
            string select_string = "SELECT uri, collection FROM wwwdocs WHERE uri IS \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = select_string;
            SQLiteDataReader dr = comm.ExecuteReader();

            if (!dr.HasRows)
                return new BoolResponse { Status = "200", Value = false };

            dr.Read();
            return new BoolResponse { Status = "200", Value = dr.GetBoolean(1) };
        }

        public override Backend.UriListResponse GetChildren(uri uri)
        {
            string select_string = "SELECT uri FROM wwwdocs WHERE parent is \'" + uri.ToString() + "\'";
            SQLiteCommand comm = conn.CreateCommand();
            comm.CommandText = select_string;
            SQLiteDataReader dr = comm.ExecuteReader();

            UriListResponse resp = new UriListResponse();
            resp.Status = "200";
            while (dr.Read())
                resp.Value.Add(uri.Parse(dr.GetString(0)));

            return resp;
        }
    }
}
